<?php

/**
 * Base Authentication class with common methods and properties.
 *
 * PHP versions 5
 * CAKEPHP versions 2.x
 * 
 * Green CMS - Content Management System and Framework Powerfull by Cakephp
 * Copyright 2012, GREEN GLOBAL CO., LTD (toancauxanh.vn)
 * 
 * CakePHP(tm) :  Rapid Development Framework (http://www.cakephp.org)
 * Copyright 2005-2011, Cake Software Foundation, Inc. (http://www.cakefoundation.org)
 *
 * Licensed under The MIT License
 * Redistributions of files must retain the above copyright notice.
 *
 * @author        Technology Lab No.I <tech1@toancauxanh.vn>
 * @link          
 * @package       Controller.Component.Auth
 * @since         Green v 1.0
 * @license       MIT License (http://www.opensource.org/licenses/mit-license.php)
 */
App::uses('Security', 'Utility');
App::uses('CakeSession', 'Model/Datasource');

/**
 * Base Authentication class with common methods and properties.
 *
 * @package       Cake.Controller.Component.Auth
 */
abstract class BaseAuthenticate {

    /**
     * Settings for this object.
     *
     * - `fields` The fields to use to identify a user by.
     * - `patterns` If you want login with username by many field then defined list field and regexp, 
     *    i.e. array('email'=>'/\w{1,}[@][\w\-]{1,}([.]([\w\-]{1,})){1,3}$/','id'=>'/^\d{1,}$/')
     * - `userModel` The model name of the User, defaults to User.
     * - `scope` Additional conditions to use when looking up and authenticating users,
     *    i.e. `array('User.is_active' => 1).`
     * - `recursive` The value of the recursive key passed to find(). Defaults to 0.
     * - `contain` Extra models to contain and store in session.
     *
     * @var array
     */
    public $settings = array(
        'fields' => array(
            'username' => 'username',
            'password' => 'password'
        ),
        'patterns' => array(),
        'userModel' => 'User.User',
        'scope' => array(),
        'recursive' => null,
        'contain' => null
    );

    /**
     * A Component collection, used to get more components.
     *
     * @var ComponentCollection
     */
    protected $_Collection;

    /**
     * Constructor
     *
     * @param ComponentCollection $collection The Component collection used on this request.
     * @param array $settings Array of settings to use. BaseAntidetect The Anti detect class set in key antidetect.
     */
    public function __construct(ComponentCollection $collection, $settings) {
        $this->_Collection = $collection;
        $this->settings = Set::merge($this->settings, $settings);
    }

    /**
     * Find a user record using the standard options.
     *
     * @param string $username The username/identifier.
     * @param string $password The unhashed password.
     * @return Mixed Either false on failure, or an array of user data.
     */
    protected function _findUser($username, $password) {
        $fields = $this->settings['fields'];
        $Model = ClassRegistry::init($this->settings['userModel']);

        $event = new CakeEvent('Component.Authenticate.before', $this, array($Model, $username));
        $this->getEventManager()->dispatch($event);

        if ($event->isStopped()) {
            return false;
        }

        $useField = $fields['username'];
        if (!empty($this->settings['patterns'])) {
            foreach ($this->settings['patterns'] as $fieldName => $pattern) {
                if (preg_match($pattern, $username)) {
                    $useField = $fieldName;
                    break;
                }
            }
        }

        $conditions = array(
            $Model->alias . '.' . $useField => $username
        );
        if (!is_null($password)) {
            $conditions[$Model->alias . '.' . $fields['password']] = $this->_password($password);
        }

        if (!empty($this->settings['scope'])) {
            $conditions = array_merge($conditions, $this->settings['scope']);
        }

        $result = $Model->find('first', array(
            'conditions' => $conditions,
            'recursive' => $this->settings['recursive'],
            'contain' => $this->settings['contain']));

        if (empty($result) || empty($result[$Model->alias])) {
            $result = false;
            $this->getEventManager()->dispatch(new CakeEvent('Component.Authenticate.error', $this, array($Model, $username)));
        } else {

            $user = $result[$Model->alias];
            unset($user[$fields['password']]);
            unset($result[$Model->alias]);
            $result = array_merge($user, $result);

            $this->getEventManager()->dispatch(new CakeEvent('Component.Authenticate.after', $this, array($Model, &$result)));
        }

        return $result;
    }

    /**
     * Hash the plain text password so that it matches the hashed/encrytped password
     * in the datasource.
     *
     * @param string $password The plain text password.
     * @return string The hashed form of the password.
     */
    protected function _password($password) {
        return Security::hash($password, null, true);
    }

    /**
     * Authenticate a user based on the request information.
     *
     * @param CakeRequest $request Request to get authentication information from.
     * @param CakeResponse $response A response object that can have headers added.
     * @return mixed Either false on failure, or an array of user data on success.
     */
    abstract public function authenticate(CakeRequest $request, CakeResponse $response);

    /**
     * Validate data was Authenticated.
     *
     * @param array $user data to validate.
     * @return mixed Either false on failure, true on success or null on skip
     */
    public function validate($user) {
        $field = null;
        $Model = ClassRegistry::init($this->settings['userModel']);
        foreach (array('updated', 'modified') as $_field) {
            if (isset($user[$_field])) {
                $field = $_field;
                break;
            }
        }
        if ($field) {
            $count = $Model->find('count', array(
                'conditions' => array(
                    $Model->primaryKey => $user[$Model->primaryKey],
                    $field => $user[$field]
                ),
                'recursive' => -1));

            if (empty($count)) {
                return false;
            }
        }

        $event = new CakeEvent('Component.Authenticate.validate', $this, array($Model, $user));
        $this->getEventManager()->dispatch($event);

        if ($event->isStopped()) {
            return false;
        }

        return true;
    }

    /**
     * Allows you to hook into AuthComponent::logout(),
     * and implement specialized logout behaviour.
     *
     * All attached authentication objects will have this method
     * called when a user logs out.
     *
     * @param array $user The user about to be logged out.
     * @return void
     */
    public function logout($user) {
        
    }

    /**
     * Get a user based on information in the request.  Primarily used by stateless authentication
     * systems like basic and digest auth.
     *
     * @param CakeRequest $request Request object.
     * @return mixed Either false or an array of user information
     */
    public function getUser($request) {
        return false;
    }

    /**
     * Returns the CakeEventManager manager instance that is handling any callbacks
     *
     * @return CakeEventManager
     */
    public function getEventManager() {
        return $this->_Collection->getEventManager();
    }

}
